package network;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

/**
 * date: 2023-2-28
 * UDP 版本的回显服务器
 */
public class UdpEchoServer {
    /*
        网络编程：本质上是要操作网卡
        但是网卡不方便直接操作，在操作系统内核中，使用了一种特殊的叫做“socket”这样的文件来抽象表示网卡
        因此进行网络通信，势必需要先有一个 socket 对象
     */
    private DatagramSocket socket = null;

    /*
        对于服务器来说，创建 socket 对象的同时，要让它绑定一个具体的端口号
        服务器一定要关联上一个具体的端口的！！！
        服务器是网络传输中，被动的一方，如果是操作系统随机分配的端口，此时客户端就不知道这个端口是啥了，也无法进行分配
     */
    public UdpEchoServer(int port) throws SocketException { // 写一个构造方法实例化 socket 对象
        socket = new DatagramSocket(port); // 实例化 socket 对象
    }

    public void start() throws IOException {
        System.out.println("服务器启动！");
        // 服务器不是只给一个客户提供服务就完了，需要服务很多客户端
        while (true) {
            /*
                （1）while 会快速循环，每循环一次就会处理一次 请求-响应。
                如果客户端发来请求了，receive 就能顺利读出来，如果没法请求，此时 receive 就会阻塞。
                这个东西就相当于 Scanner 的操作（读取控制台数据）
                （2）如果多个客户端并且请求发的很快---就是所谓的“高并发”。while就会处理不过来，怎么解决？---》使用多线程，
                更充分的调动计算机的硬件资源。
            */
            /*
            只要有客户过来，就可以提供服务
            1、读取客户端发来的请求是啥
                receive 方法的参数是一个输出型参数，需要先构造好一个空白的 DatagramPacket 对象，交给 receive 来进行补充
                DatagramPacket ----> 数据报
             */
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);
            /*
            此时这个 DatagramPacket 是一个特殊的对象，并不方便直接进行处理，可以把这里包含的数据拿出来，构造成一个字符串
            此处给的最大长度是 4096，但那是不一定用满了，可能只用了一小部分。因此构造字符串，哪些使用了就构造哪些，就通过getLength
            获取实际的数据报的长度，只把这个实际的长度构造成字符串即可。
             */
            String request = new String(requestPacket.getData(),0, requestPacket.getLength());

            // 2、根据请求计算 响应 --> response
            String response = process(request);

            /*
            3、把响应写回到客户端，send 的参数也是 DatagramPacket，需要把这个 Packet 对象构造好
             此处构造的响应对象，不能是用空字节数组构造了，而是使用响应数据来构造
             responsePacket 不认识字符，只认字节
                            response.length -》 字符的个数 ， response.getBytes().length -》字节的个数
             */
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
                    requestPacket.getSocketAddress());
            // requestPacket.getSocketAddress() 获取到客户端的 IP 和端口号。这两个信息本身就在 requestPacket 中
            socket.send(responsePacket);

            // 4、打印一下，当前这次请求响应的处理中间结果
            // requestPacket.getAddress().toString() --- 获取到 packet 里边的IP
            // requestPacket.getPort() --- 获取端口号
            // request --- 请求
            // response --- 响应
            System.out.printf("[%s:%d] req: %s; resp: %s\n", requestPacket.getAddress().toString(),
                    requestPacket.getPort(), request, response);
        }
    }

    /**
     * 根据请求计算响应
     * @param request 请求
     * @return 回显服务器是 请求啥就返回啥
     */
    public String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        // 端口号的指定，可以随便指定 --- 1024->65535 这个范围随便挑选个数字
        UdpEchoServer server = new UdpEchoServer(9090);
        server.start();

    }
}
